/*
 *  Vector.h
 *  NSE_SOLVER
 *
 *  Created by Patrick Zulian on 12/22/10.
 *  Copyright 2010 ICS - USI - Lugano. All rights reserved.
 *
 */


#ifndef NSE_SOLVER_VECTOR_H
#define NSE_SOLVER_VECTOR_H

#include "Base.h"
#include "Utils.h"
#include "VectorHelper.h"


namespace nsesolver  {
	
	
	template< int D, typename T = double, class Impl = VectorHelper< T > >
	class Vector {
	private:


		T values[D];
	public:
		//virtual ~Vector() { };
		Vector() { }
		
		
		Vector(const T values[])
		{
			using namespace utils;
			Copy< T, D >(this->values, values);
		}
		
		Vector(const Vector &other)
		{
			utils::Copy< T, D >(values, other.values);
		}
		
		static
		const Vector random(const T range)
		{
			Vector v;
			Impl::random(v, range);
			return v;
		}
		
		
		static
		const Vector random(const T min, const T max)
		{
			Vector v;
			Impl::random(v, min, max);
			return v;
		}
		

		/**
		 *	@param d the axis for which we want to query the vector
		 *	@return a reference to the coordinate on the d axis
		 */
		inline 
		T &operator[](const int d)  
		{
			assert(d >= 0 && d < D);
			return values[d];
		}
		
		
		/**
		 *	@param d the axis for which we want to query the vector
		 *	@return the value of the coordinate on the d axis
		 */
		inline 
		const T operator[](const int d) const
		{
			assert(d >= 0 && d < D);
			return values[d];
		}
		
		inline int size() const
		{
			return D;
		}
		
		Vector & operator = (const Vector &other)
		{
			if (this == &other) {
				return *this;
			}
			
			utils::Copy< T, D >(values, other.values);
			
			return *this;
		}
		
		// --------------------------------------- Magnitude Functions ---------------------------//
		
		/**
		 *	@return the euclidan norm of the vector ||x||_2
		 */
		inline 
		const T norm() const
		{
			return Impl::norm(*this);
		}
		
		/**
		 *	@return the euclidan norm squared, thus <x,x> or ||x||^2_2
		 */
		inline 
		const T squaredNorm() const
		{
			return Impl::squaredNorm(*this);
		}
		
		inline 
		Vector & normalize()
		{
			Impl::normalize(*this);
			return *this;
		}
	
		inline
		const T dot(const Vector &other) const
		{
			return Impl::dotProduct(*this, other);
		}
		
		
		// --------------------------------------- Distance Functions ---------------------------//
		
		
		inline 
		const T squaredDistance(const Vector &other) const
		{
			return Impl::squaredDistance(*this, other);
		}
		
		
		inline 
		const T distance(const Vector &other) const
		{
			return Impl::distance(*this, other);
		}
		
		
		//------------------------------- Sum operators --------------------------------------------------//
		
		
		/**
		 *	@param other the other point
		 *	@return a point which is an element wise sum of this and other
		 */
		inline
		const Vector operator+(const Vector &other) const
		{
			return Impl::sum(*this, other);
		}
		
		/**
		 *	@param other the other point of which we add element wise each component
		 *	to this
		 */
		inline
		Vector & operator += (const Vector &other) 
		{
			Impl::inPlaceSum(*this, other);
			return *this;
		}
		
		/**
		 *	@param other the other point
		 *	@return a point which is an element wise sum of this and other
		 */
		inline
		const Vector operator - (const Vector &other) const
		{
			return Impl::subtract(*this, other);
		}
		
		/**
		 *	@param other the other point of which we add element wise each component
		 *	to this
		 */
		inline
		Vector &  operator -= (const Vector &other) 
		{
			Impl::inPlaceSubtract(*this, other);
			return *this;
		}
		
		
		//-------------------------------- Multiplication Operators -----------------------------------//
		
		/**
		 *	@param lambda a scalar for which we multiply the point
		 *	@return the scaled point
		 */
		inline
		const Vector operator*(const T scaleFactor) const
		{
			return Impl::scale(*this, scaleFactor);
		}
		
		/**
		 *	same as v*\lambda
		 */
		friend 
		const Vector operator*(const T scaleFactor, const Vector &v)
		{
			return v * scaleFactor;
		}
		
		
		/**
		 *	Scales this vector by lambda
		 *	@param lambda the scalar parameter
		 */
		inline
		Vector operator *= (const T scaleFactor)
		{
			Impl::inPlaceScale(*this, scaleFactor);
			return *this;
		}
		
		
		inline Vector operator/(const T scaleFactor)
		{
			return (*this) * (1.0/scaleFactor);
		}
		
		friend 
		const Vector operator/(const T scaleFactor, const Vector &v)
		{
			return v / scaleFactor;
		}
		
		
		inline Vector & operator /=(const T scaleFactor)
		{
			(*this) *= (1.0/scaleFactor);
			return *this;
		}
		
		/**
		 *	Element wise multiplication for lambda. !! Do not confuse with the scalar product
		 *	@param lambda the scalar for which we scale the point
		 *	@return the scaled vector v*\lambda
		 */
		inline
		const Vector< D, T > operator*(const Vector &other) const
		{
			return Impl::multiply(*this, other);
		}
		
		/**
		 *	Element wise multiplication for lambda. !! Do not confuse with the scalar product
		 *	@param lambda the scalar for which we scale this point
		 */
		inline
		Vector & operator *= (const Vector &other)
		{
			Impl::inPlaceMultiply(*this, other);
			return *this;
		}
		
		/** ------------------------------------------------------- UTILS ------------------------------------------------- */
		
		/**
		 * @param val the value which every coordinate of this object will be set to 
		 */
		inline
		Vector & allSet(const T value)
		{
			Impl::allSet(*this, value);
			return *this;
		}
		
		/// all coordinates are set to 0
		inline
		Vector &  zeros()
		{
			allSet(0);
			return *this;
		}
		
		/// all coordinates are set to the - max number of type T 
		inline 
		Vector & allSetMin()
		{
			return allSet( -std::numeric_limits< T >::max() );
			
		}
		
		/// all coordinates are set to the max number of type T 
		inline
		Vector & allSetMax()
		{
			return allSet( std::numeric_limits< T >::max() );
		}
		
		/**
		 * @param other the point from which the smaller values are taken for each dimension (e.g this.x > other.x; then this.x = other.x) 
		 */
		inline
		Vector & takeSmaller(const Vector &other)
		{
			for (int d = 0; d < D; d++) {
				(*this)[d] = std::min< T >( (*this)[d], other[d] );
			}
			return *this;
		}
		
		/**
		 * @param other the point from which the greater values are taken for each dimension (e.g this.x < other.x; then this.x = other.x) 
		 */
		inline
		Vector & takeGreater(const Vector &other)
		{
			for (int d = 0; d < D; d++) {
				(*this)[d] = std::max< T >( (*this)[d], other[d] );
			}
			return *this;
		}
		
		
		
		/** ------------------------------------------------------- DEBUG -----------------------------------------------------------------------?*/

		//Output
		friend
		std::ostream & operator <<(std::ostream &os, const Vector &v)
		{
			for (int d = 0; d < D; d++) {
				os << v[d];
				if (d < D -1) {
					os << " ";
				}
			}
			
			os << std::endl;
			
			return os;
		}
		
		
	};
	
}

#endif



